import { ComponentPropsOptions, ExtractPropTypes, SetupContext, defineComponent, getCurrentInstance, provide, inject } from 'vue';
import { ComponentEvent, getComponentEmit, useEvent } from './useEvent';

export function designComponent<
  PropsOptions extends Readonly<ComponentPropsOptions>,
  Props extends Readonly<ExtractPropTypes<PropsOptions>>,
  Emits extends { [k: string]: (...args: any[]) => boolean },
  Refer,
>(
  options: {
    name?: string,
    props?: PropsOptions,
    provideRefer?: boolean,
    emits?: Emits,
    setup: (parameter: { props: Props, event: ComponentEvent<Emits>, setupContext: SetupContext }) => {
      refer?: Refer,
      render: () => any
    }
  }
) {
  const { setup, provideRefer, emits, ...leftOptions } = options;

  return {
    ...defineComponent({
      ...leftOptions,
      emits: getComponentEmit(emits),
      setup(props: Props, setupContext: any) {
        const ctx = getCurrentInstance()!;
        const event = useEvent<Emits>(emits!);
        
        if (!setup) {
          console.error('designComponent: setup is required!')
          return () => null
        }

        const { refer, render } = setup({props, event, setupContext});
        // ctx._refer = refer
        if (!!refer) {
          const duplicateKey = Object.keys(leftOptions.props || {})
            .find(i => Object.prototype.hasOwnProperty.call(refer as any, i))
          if (!!duplicateKey) {
            console.error(`designComponent: duplicate key ${duplicateKey} in refer`)
          } else {
            Object.assign(ctx.proxy, refer)
          }
        }

        if (provideRefer) {
          if (!leftOptions.name) {
            console.error('designComponent: name is required when provideRefer is true!')
          } else {
            provide(`@@${leftOptions.name}`, refer)
          }
        }
        return render
      }
    } as any),
    use: {
      // ref: (refName: string) => {
      //   const ctx = (getCurrentInstance() as any).ctx
      //   return {
      //     get value() {
      //       return ((ctx as any).$refs[refName].$._refer) as Refer | null
      //     }
      //   }
      // },
      ref: (refName: string) => {
        const ctx = getCurrentInstance()!;
        return {
          get value() {
            return ctx.refs[refName] as Refer | null
          }
        }
      },
      inject: (defaultValue?: any) => {
        return inject(`@@${leftOptions.name}`, defaultValue) as Refer
      }
    }
  }
}